package com.bugsnag.ohos;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * ObjectJsonStreamer
 *
 * @since 2021-06-29
 */
public class ObjectJsonStreamer {
    private static final String REDACTED_PLACEHOLDER = "[REDACTED]";
    private static final String OBJECT_PLACEHOLDER = "[OBJECT]";
    private Set<String> redactedKeys = Collections.singleton("password");

    public Set<String> getRedactedKeys() {
        return redactedKeys;
    }

    public void setRedactedKeys(Set<String> redactedKeys) {
        this.redactedKeys = redactedKeys;
    }

    private void objectToStream(Object obj, JsonStream writer) throws IOException {
        objectToStream(obj, writer, false);
    }

    /**
     * Write complex/nested values to a JsonStreamer
     *
     * @param obj
     * @param writer
     * @param shouldRedactKeys
     * @throws IOException
     */
    public void objectToStream(Object obj, JsonStream writer, boolean shouldRedactKeys) throws IOException {
        if (obj == null) {
            writer.nullValue();
        } else if (obj instanceof String) {
            writer.value((String) obj);
        } else if (obj instanceof Number) {
            writer.value((Number) obj);
        } else if (obj instanceof Boolean) {
            writer.value((Boolean) obj);
        } else if (obj instanceof JsonStream.Streamable) {
            ((JsonStream.Streamable) obj).toStream(writer);
        } else if (obj instanceof Date) {
            writer.value(DateUtils.toIso8601((Date) obj));
        } else if (obj instanceof Map) {
            mapToStream(writer, (Map) obj, shouldRedactKeys);
        } else if (obj instanceof Collection) {
            collectionToStream(writer, (Collection) obj);
        } else if (obj.getClass().isArray()) {
            arrayToStream(writer, obj);
        } else {
            writer.value(OBJECT_PLACEHOLDER);
        }
    }

    private void mapToStream(JsonStream writer, Map obj, boolean shouldRedactKeys) {
        try {
            writer.beginObject();
            Iterator it = obj.entrySet().iterator();
            Object key;
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                key = entry.getKey();
                if (key instanceof String) {
                    writer.name((String) key);
                    if (shouldRedactKeys && isRedactedKey((String) key)) {
                        writer.value(REDACTED_PLACEHOLDER);
                    } else {
                        objectToStream(entry.getValue(), writer, shouldRedactKeys);
                    }
                }
            }
            writer.endObject();
        } catch (IOException e) {
            e.getMessage();
        }
    }

    private void collectionToStream(JsonStream writer, Collection obj) {
        try {
            writer.beginArray();
            Iterator it = obj.iterator();
            while (it.hasNext()) {
                objectToStream(it.next(), writer);
            }
            writer.endArray();
        } catch (IOException e) {
            e.getMessage();
        }
    }

    private void arrayToStream(JsonStream writer, Object obj) {
        try {
            writer.beginArray();
            int length = Array.getLength(obj);
            int i = 0;
            while (i < length) {
                objectToStream(Array.get(obj, i), writer);
                i += 1;
            }
            writer.endArray();
        } catch (IOException e) {
            e.getMessage();
        }
    }

    private boolean isRedactedKey(String key) {
        return redactedKeys.stream().findAny().equals(key);
    }
}
